/*
    作者：zx
    时间：2023.2.1
    描述：tty模块负责抽象硬件细节
*/

#include "dev/tty.h"
#include "dev/dev.h"
#include "dev/console.h"
#include "dev/kbd.h"
#include "tools/log.h"


static tty_t tty_devs_table[TTY_TABLE_SIZE];
static int curr_tty_idx = 0;

static tty_t * get_tty_from_dev(device_t *dev)
{
    int tty_idx = dev->minor;
    if(tty_idx < 0 || tty_idx >= TTY_TABLE_SIZE || dev->open_count==0)
    {
        log_printf("get_tty_from_dev failed. tty id = %d", tty_idx);
        return 0;
    }

    return tty_devs_table + tty_idx;
}

static void tty_fifo_init(tty_fifo_t * fifo, char * buff, int size)
{
    fifo->buff = buff;
    fifo->size = size;
    fifo->read = fifo->write = 0;
    fifo->count = 0;
}

int tty_fifo_put(tty_fifo_t * fifo, char ch)
{
    irq_status_t status = irq_enter_protection();
    if(fifo->count == fifo->size)
    {
        irq_quit_protection(status);
        return -1;
    }
 
    fifo->buff[fifo->write++] = ch;
    if (fifo->write >= fifo->size) {
		fifo->write = 0;
	}
    fifo->count++;
    irq_quit_protection(status);
    return 0;
}

int tty_fifo_get(tty_fifo_t * fifo, char * ch)
{
    irq_status_t status = irq_enter_protection();
    if(fifo->count == 0)
    {
        // log_printf("tty fifo get failed. fifo is empty.");
        irq_quit_protection(status);
        return -1;
    }
    *ch = fifo->buff[fifo->read++];
    if (fifo->read >= fifo->size) {
		fifo->read = 0;
	}
    fifo->count--;
    irq_quit_protection(status);
    return 0;
}


int tty_open(device_t * dev)
{
    int tty_idx = dev->minor;
    if(tty_idx < 0 || tty_idx >= TTY_TABLE_SIZE)
    {
        return -1;
    }
    
    tty_t * tty = tty_devs_table + tty_idx;
    tty_fifo_init(&tty->infifo, tty->in_buff, TTY_IN_BUFF_SIZE);
    tty_fifo_init(&tty->outfifo, tty->out_buff, TTY_OUT_BUFF_SIZE);
    sema_init(&tty->in_semaphore, 0);
    sema_init(&tty->out_semaphore, TTY_OUT_BUFF_SIZE);
    tty->console_idx = tty_idx;
    tty->in_flag = TTY_IN_CRLF | TTY_IN_ECHO;
    tty->out_flag = TTY_OUT_CRLF;
    kbd_init();
    console_init(tty_idx);
    return 0;
}

// 设备/目的地址/需要写入的数据/大小
int tty_write(device_t * dev, int addr, char * buff, int size)
{
    if(size < 0) 
    {
        return -1;
    }
    tty_t * tty = get_tty_from_dev(dev);

    if(!tty)
    {
        return -1;
    }

    int len = 0;
    while (size)
    {
        char ch = *buff++;
        
        if(ch == '\n' && (tty->out_flag & TTY_OUT_CRLF))
        {
            sema_wait(&tty->out_semaphore);
            int err = tty_fifo_put(&tty->outfifo, '\r');
            if(err < 0) 
            {
                break;
            }
        }

        sema_wait(&tty->out_semaphore);
        int err = tty_fifo_put(&tty->outfifo, ch);
        if(err < 0) 
        {
            break;
        }

        size--;
        len++;
        console_write(tty); 
    }
    return len;
}


// 设备/来自地址/目的地址/大小
int tty_read(device_t * dev, int addr, char * buff, int size)
{
    if(size < 0)
    {
        return -1;
    }

    tty_t * tty = get_tty_from_dev(dev);
    if(!tty)
    {
        return -1;
    }

    int len = 0;
    char * pbuff = buff;
    while(size)
    {
        sema_wait(&tty->in_semaphore);

        char ch;
        tty_fifo_get(&tty->infifo, &ch);
        if(ch == '\n') 
        {
            if((tty->in_flag & TTY_IN_CRLF) && len < size-1)
            {
                *pbuff++ = '\r';
                len++;
            }
            *pbuff++ = ch;
            len++;
            size--;
        }
        else if(ch == 0x7f)
        {
            if(len)
            {
                len--;
                pbuff--;
            }
            else
            {
                continue;
            }
        }
        else
        {
            *pbuff++ = ch;
            len++;
            size--;
        }


        if(tty->in_flag & TTY_IN_ECHO)
        {
            tty_write(dev, 0, &ch, 1);
        }

        if(ch == '\n' || ch == '\r')
        {
            break; 
        }
    }

    return len;
}

int tty_control(device_t * dev, int cmd, int arg0, int arg1)
{
    if(cmd == TTY_CMD_ECHO)
    {
        tty_t * tty = get_tty_from_dev(dev);
        if(arg0 == TTY_CMD_ECHO_CLOSE)  tty->in_flag &= ~TTY_IN_ECHO;
        else if(arg0 == TTY_CMD_ECHO_OPEN)  tty->in_flag |= TTY_IN_ECHO;
    }
    return 0;
}

void tty_close(device_t * dev)
{

}

void tty_in(char ch)
{
    tty_t * tty = tty_devs_table + curr_tty_idx;
    if(sema_value(&tty->in_semaphore) >= TTY_IN_BUFF_SIZE)
    {
        return ;
    }

    tty_fifo_put(&tty->infifo, ch);
    sema_notify(&tty->in_semaphore);
}

void tty_select(int idx)
{
    if(idx == curr_tty_idx){
        return ;
    }
    curr_tty_idx = idx;
    console_select(curr_tty_idx);
}

device_desc_t dev_tty_desc = 
{
    .name = "tty",
    .major = DEV_TTY,
    .open = tty_open,
    .read = tty_read,
    .write = tty_write,
    .control = tty_control,
    .close = tty_close,
};

